/**
 * 
 */
package cn.springsoter.core.mp.utils;

import java.beans.FeatureDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.cglib.beans.BeanCopier;

import com.baomidou.mybatisplus.core.metadata.IPage;

import lombok.extern.slf4j.Slf4j;


/**
 *  DTO，VO，entity 转换工具类
 *
 *  @param1 oldClass 原数据--Dto，Vo，entity
 *  @param1 newClass 转换为--Dto，Vo，entity
 *  <P>
 *     1.0.1之前: 采用 org.springframework.beans.BeanUtils 包下的 BeanUtils.copyProperties(dtoEntity, newInstance);
 *     1.0.2: 更新为 cglib，动态生成set/get方法，提供缓存，同一组转换只动态生成一次，效率高
 *  </P>
 * @author kevin
 */
@Slf4j
public class BeanConvertUtil<S> {
	
	/**
     * BeanCopier的缓存
     */
    private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();

    /**
     * dot ,do ,entity 相互转换（使用cglib）
     *
     * <P>
     *  * 1、使用 cglib, 几乎无性能损耗, 和 mapstruct 和 get/set 性能不相上下,甚至更快
     *   ---- mapstruct 在 编译时生成class文件的set,get方法，功能更全，支持属性key不一致，属性类型不一致的配置替换
     *   ---- cglib 在 BeanCopier.create动态生成set/get方法,功能单一，但性能可观,注意避免频繁 BeanCopier.create, 会重复动态生成get/set,将达不到预期的性能效果
     *
     *  2、使用 springboot的
     *   ---- BeanUtils.copyProperties(oldClass, newInstance);
     *
     *  3、使用 mapstruct
     *   ---- 参考 Demo项目cn.springsoter.web.converter.LogConverter 转换类, 启动项目会动态生成 cn.springsoter.web.converter.LogConverterImpl的calss类，并生成转换方法
     * </P>
     * @param oldClass  原数据--Dto，Vo，entity
     * @param newClass  转换为--Dto，Vo，entity
     * @return E
     */
    public static <E> E convert(Object oldClass, Class<E> newClass) {
        if (oldClass == null) {
            return null;
        }
        if (newClass == null) {
            return null;
        }
        E newInstance = null;
        try {
            newInstance = newClass.newInstance();
        } catch (Exception e) {
            log.debug(e.toString());
            return null;
        }
        String key = oldClass.getClass().getName() + ":" + newClass.getName();
        BeanCopier copier = null;
        if (BEAN_COPIER_CACHE.containsKey(key)) {
            copier = BEAN_COPIER_CACHE.get(key);
        } else {
            copier = BeanCopier.create(oldClass.getClass(), newInstance.getClass(), false);
            BEAN_COPIER_CACHE.put(key, copier);
        }
        copier.copy(oldClass, newInstance, null);
        return newInstance;
    }


    /**
     * Page<Entity> 分页对象转 Page<Vo>  ( list 循环)
     * @param page
     * @param v
     * @return com.baomidou.mybatisplus.core.metadata.IPage<V>
     */
    public static <T, V> IPage<V> pageVo(IPage<T> page, Class<V> v) {
        if (page == null) {
            return null;
        }
        return page.convert(item -> BeanConvertUtil.convert(item, v));
    }


    /**
     * list<Entity> 集合对象转list<Vo> ( list 循环)
     * @param oldList
     * @param v
     * @return java.util.List<V>
     */
    public static <T, V> List<V> listVo(List<T> oldList, Class<V> v) {
        List<V> voList = new ArrayList<>();
        if (oldList != null) {
            oldList.forEach(i -> voList.add(BeanConvertUtil.convert(i, v)));
        }
        return voList;
    }


    /**
     * list<Entity> 集合对象转list<Vo> （parallelStream 并行流方式，适合大量数据下使用）
     * @param oldList
     * @param v
     * @return java.util.List<V>
     */
    public static <T, V> List<V> listVoStream(List<T> oldList, Class<V> v) {
        if (oldList == null || oldList.isEmpty()) {
            return new ArrayList<>();
        }
        return oldList.parallelStream().map(item -> (V) BeanConvertUtil.convert(item, v)).collect(Collectors.toList());
    }
	
	/**
	 * 找出null的字段
	 * 
	 * @param source
	 * @return
	 */
	public static String[] getNullPropertyNames(Object source) {
	    final BeanWrapper wrappedSource = new BeanWrapperImpl(source);
	    String[] fileds = Stream.of(wrappedSource.getPropertyDescriptors())
	            .map(FeatureDescriptor::getName)
	            .filter(propertyName -> wrappedSource.getPropertyValue(propertyName) == null)
	            .toArray(String[]::new);
	    return fileds;
	}
}
